package com.yjk1.example.myFirstSofawebBoot.framework;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.Manifest;

/**
 * @author yujiakui
 * @version 1.0
 * Email: jkyu@haiyi-info.com
 * date: 2018/11/12 15:02
 * description：动态jar 类加载器
 **/
public class DynamicJarClassLoader extends URLClassLoader {

    /**
     * logger
     */
    private final static Logger LOGGER = LoggerFactory.getLogger(DynamicJarClassLoader.class);

    /**
     * 缓存对应的Jar url连接器
     */
    private List<JarURLConnection> cachedJarFiles = new ArrayList<JarURLConnection>();

    /**
     * jar包对应的清单列表
     */
    private Map<URL, Manifest> manifestMap = new HashMap<URL, Manifest>();

    /**
     * 依赖的插件名称列表：这个是对应的classLoader依赖
     */
    private List<String> dependencyPluginNames = null;

    /**
     * 动态jar包管理服务
     */
    private DynamicJarManageService dynamicJarManageService;

    // TODO 哪些插件依赖我这


    /**
     * 构造函数
     *
     * @param parentClassLoader
     * @param dependencyPluginNames
     */
    public DynamicJarClassLoader(ClassLoader parentClassLoader,
                                 DynamicJarManageService dynamicJarManageService, List<String> dependencyPluginNames) {
        this(new URL[]{}, parentClassLoader);
        this.dependencyPluginNames = dependencyPluginNames;
        this.dynamicJarManageService = dynamicJarManageService;
    }

    public DynamicJarClassLoader() {
        this(new URL[]{}, null);
    }

    /**
     * 构造函数
     *
     * @param urls
     * @param parentClassLoader
     */
    public DynamicJarClassLoader(URL[] urls, ClassLoader parentClassLoader) {
        super(urls, null == parentClassLoader ?
                Thread.currentThread().getContextClassLoader() : parentClassLoader);
    }

    /**
     * 将指定的文件url添加到类加载器的classpath中去，并缓存jar connection，方便以后卸载jar
     *
     * @param url 一个可想类加载器的classpath中添加的文件url
     */
    public void addURLFile(URL url) {
        try {
            // 打开并缓存文件url连接
            URLConnection uc = url.openConnection();
            if (uc instanceof JarURLConnection) {
                uc.setUseCaches(true);
                Manifest manifest = ((JarURLConnection) uc).getManifest();
                cachedJarFiles.add((JarURLConnection) uc);
                manifestMap.put(url, manifest);
            }
        } catch (Exception e) {
            System.err.println("Failed to cache plugin JAR file: " + url.toExternalForm());
            e.printStackTrace();
        }
        addURL(url);
    }

    /**
     * 卸载jar包
     */
    public void unloadJarFiles() {
        for (JarURLConnection url : cachedJarFiles) {
            try {
                System.err.println("Unloading plugin JAR file " + url.getJarFile().getName());
                url.getJarFile().close();
                url = null;
            } catch (Exception e) {
                System.err.println("Failed to unload JAR file\n" + e);
            }
        }
    }

    @Override
    public Class<?> loadClass(String className) throws ClassNotFoundException {
        // second check whether it's already been loaded
        Class<?> loadedClass = findLoadedClass(className);
        if (loadedClass != null) {
            LOGGER.info("Found loaded class '{}'", className);
            return loadedClass;
        }

        // try to load from parent
        try {
            return super.loadClass(className);
        } catch (ClassNotFoundException e) {
            // try next step
        }

        LOGGER.info("Couldn't find class '{}' in parent. Delegating to plugin classpath", className);

        // nope, try to load locally
        try {
            loadedClass = findClass(className);
            LOGGER.info("Found class '{}' in plugin classpath", className);
            return loadedClass;
        } catch (ClassNotFoundException e) {
            // try next step
        }

        // look in dependencies
        loadedClass = loadClassFromDependencies(className);
        if (loadedClass != null) {
            LOGGER.info("Found class '{}' in dependencies", className);
            return loadedClass;
        }

        throw new ClassNotFoundException(className);
    }

    /**
     * 从依赖中加载class
     *
     * @param className
     * @return
     */
    private Class<?> loadClassFromDependencies(String className) {

        if (!CollectionUtils.isEmpty(dependencyPluginNames)) {
            for (String pluginName : dependencyPluginNames) {
                try {
                    ClassLoader classLoader = this.dynamicJarManageService.getPluginClassLoader(pluginName);
                    if (null == classLoader) {
                        throw new RuntimeException("依赖插件pluginName=" + pluginName + "对应的classLoader不存在");
                    }
                    Class clazz = classLoader.loadClass(className);
                    if (null != clazz) {
                        return clazz;
                    }
                } catch (ClassNotFoundException e) {
                    // ignore
                }
            }
            LOGGER.error("name={}对应的classLoader={}加载不到", className, dependencyPluginNames);
        }
        return null;
    }

    public List<String> getDependencyPluginNames() {
        return dependencyPluginNames;
    }

    public Map<URL, Manifest> getManifestMap() {
        return manifestMap;
    }


}
